const Response = require("../../class/Response");
WSC.get(/\/bds\/sync\/(.*)/, (wsclient, req, res) => {
    if (!CONF.CONFIG.get("SyncConfig")["Enabled"]) {
        return false; // 未开启同步
    }
});

class Context {
    constructor(currentState, content) {
        this.currentState = currentState;
        this.content = content;
    }

    setState(state) {
        this.currentState = state;
        return this;
    }

    clear() {
        this.content = {};
        return this;
    }

    reset(content) {
        this.content = content;
        return this;
    }
}

const STATES = {
    /** ⏳等待BDS登录结果 */ INITIAL: "INITIAL", 
    /** 📡请求预同步数据 */ REQUEST_PRE_SYNC: "REQUEST_PRE_SYNC",
    /** 🔍请求玩家数据 */ REQUEST_PLAYER_DATA: "REQUEST_PLAYER_DATA",
    /** ⚙️应用玩家数据 */ APPLY_PLAYER_DATA: "APPLY_PLAYER_DATA",
    /** 🚀同步传送 */ SYNC_TRANSFER: "SYNC_TRANSFER", 
    /** ❌同步失败 */ SYNC_FAILED: "SYNC_FAILED",    
    /** ✅同步成功 */ SYNC_SUCCESS: "SYNC_SUCCESS",  
};

WSC.get("/bds/playerLogin/result", async (wsclient, req, res) => {
    let context = new Context(STATES.INITIAL, { wsclient, req, res });
    try {
        while (context?.currentState){
            await stateHandlers[context.currentState]({context, ...context.content});
        }
    }catch(e){
        WSC.logger.error(`[数据同步失败]`, e);
        context.reset({playerInfo: req.body.data, reason: `登录同步失败, ${e.message}`, level: 'error'});
        context.setState(STATES.SYNC_FAILED);
        await stateHandlers[context.currentState]({context, ...context.content});
    }
});

const stateHandlers = {
    /** ⏳等待BDS登录结果 */
    [STATES.INITIAL]: async ({ context, wsclient, req, res }) => {
        if (!CONF.CONFIG.get("SyncConfig")["Enabled"]) {
            return null;
        } else if (req.body.statusCode != 0) {
            /** 🔄️❌STATES.SYNC_FAILED 同步失败 - 玩家登记失败 */
            context.reset({playerInfo: req.body.data, reason: '玩家登记失败'});
            return context.setState(STATES.SYNC_FAILED);
        } else {
            /** 🔄️📡STATES.REQUEST_PRE_SYNC 请求预同步数据 */
            context.reset({req});
            return context.setState(STATES.REQUEST_PRE_SYNC);
        }
    },

    /** 📡请求预同步数据 */
    [STATES.REQUEST_PRE_SYNC]: async ({ context, req}) => {
        let playerInfo = {
            xuid: req.body.data.xuid,
            uuid: req.body.data.uuid,
            realName: req.body.data.realName,
        };
        req = (await WSC.client.sendJsonAsync(
            new Response()
                .setUrl("/bds/sync/loginSync/getEnvironmentInfo")
                .setData(playerInfo)
        )).req;
        /** 🔄️❌同步失败 - 获取协调服务端在线信息失败 */
        if (req?.body?.statusCode != 0) {
            context.reset({playerInfo: playerInfo, reason: '登录同步失败, 获取协调服务端在线信息失败'});
            return context.setState(STATES.SYNC_FAILED);
        }
        
        let plSyncTemp = req.body.data.playerSyncTemp; // 玩家同步缓存信息
        let onlineClientsInfo = req.data.onlineClientsInfo;
        
        /** 🔄️❌同步失败 - 主服未连接到WS协调服务端 */
        if (Object.values(onlineClientsInfo).find(clientInfo=>clientInfo?.bds?.syncType == 'main') == null){
            context.reset({playerInfo: req.body.data, reason: '登录同步失败, 主服未连接到WS协调服务端'});
            return context.setState(STATES.SYNC_FAILED);
        }

        /** 🔄️🔍请求玩家数据 - 无玩家上次同步缓存信息需要请求*/
        if (plSyncTemp == null){
            context.reset({req: req});
            return context.setState(STATES.REQUEST_PLAYER_DATA);
        }

        /** 🔄️✅同步成功 - 上次登录是本服务器 则无需请求数据 */
        if (plSyncTemp.lastLogoutBDSuuid == CONF.CONFIG.get('Websocket').BDSUuid){
            context.reset({playerInfo: req.body.data});
            return context.setState(STATES.SYNC_SUCCESS);
        }

        // 上次登录不是本服务器
        if (plSyncTemp.lastLogoutBDSuuid != CONF.CONFIG.get('Websocket').BDSUuid){

            /** 🔄️🔍请求玩家数据 - 上次正常退出 */
            if (plSyncTemp.lastLogoutSuccess == true){
                context.reset({req: req});
                return context.setState(STATES.REQUEST_PLAYER_DATA);
            }
            
            let lastLogoutBDSClientInfo = Object.values(onlineClientsInfo)
                .find(client=>plSyncTemp.lastLogoutBDSuuid == client?.bds?.uuid);

            /** 🔄️🚀同步传送 - 上次意外退出传送到上次登录的服务器 */
            if (lastLogoutBDSClientInfo != null){
                context.reset({
                    playerInfo: req.body.data, 
                    reason: '上次意外退出, 正在传送到上次登录的服务器', 
                    ip: lastLogoutBDSClientInfo.bds.ip, 
                    port: lastLogoutBDSClientInfo.bds.port
                });
                return context.setState(STATES.SYNC_TRANSFER);
            }

            /** 🔄️❌同步失败 - 上次意外退出且上次登录服务器不在线 */
            context.reset({playerInfo: req.body.data, reason: `登录同步失败，上次意外退出服务器。 等上次登录的服务器(${plSyncTemp.lastLogoutBDSname})启动后可恢复`});
            return context.setState(STATES.SYNC_FAILED);
        }
    },

    /** 🔍请求玩家数据 */
    [STATES.REQUEST_PLAYER_DATA]: async ({ context, req}) => {
        /** 🔄️✅同步成功 - 登录主服则无需请求数据 */
        if (CONF.CONFIG.get('SyncConfig')['SyncType'] == 'main'){
            context.reset({playerInfo: req.body.data});
            return context.setState(STATES.SYNC_SUCCESS);
        }
        /** 🔄️⚙️应用玩家数据 - 登录副服请求玩家数据并应用*/
        if (CONF.CONFIG.get('SyncConfig')['SyncType'] == 'sub'){
            let playerInfo = {
                xuid: req.body.data.xuid,
                uuid: req.body.data.uuid,
                realName: req.body.data.realName,
            };
            ({req} = await WSC.client.sendJsonAsync(new Response()
                .setUrl("/bds/sync/loginSync/getPlayerData")
                .setSourceId(WSC.client.info.id)
                .setSourceBdsUuid(CONF.CONFIG.get('Websocket')['BDSUuid']) // 涉及客户端间通信, 需要设置源
                .setSourceBdsServerName(CONF.CONFIG.get('Websocket')['ServerName'])
                .setData({
                    playerSyncTemp: req.body.data.playerSyncTemp,
                    onlineClientsInfo: req.body.data.onlineClientsInfo,
                    xuid: req.body.data.xuid,
                    uuid: req.body.data.uuid,
                    realName: req.body.data.realName,
                })
            ));
            /** 🔄️❌同步失败 - 获取玩家数据失败 */
            if (req == null) {
                context.reset({playerInfo: playerInfo, reason: '登录同步失败, 获取玩家数据失败, 请求超时'});
                return context.setState(STATES.SYNC_FAILED);
            }
            context.reset({req});
            return context.setState(STATES.APPLY_PLAYER_DATA);
        }

        /** 🔄️❌同步失败 - 服务器类型配置错误(应为main或sub) */
        context.reset({playerInfo: req.body.data, reason: '登录同步失败, 服务器类型配置错误(应为main或sub)'});
        return context.setState(STATES.SYNC_FAILED);
    },


    /** ⚙️应用玩家数据 */
    [STATES.APPLY_PLAYER_DATA]: async ({ context, req}) => {
        // 获取到玩家数据
        if (req.body.statusCode == 0){
            // 登录同步应用玩家数据
            let {success, statusMessage} = setPlayerData(req.body.data, req.body.data.playerData);

            /** 🔄️✅同步成功 - 应用玩家数据成功 */
            if (success){
                context.reset({playerInfo: req.body.data});
                return context.setState(STATES.SYNC_SUCCESS);
            }

            /** 🔄️❌同步失败 - 应用玩家数据失败 */
            else{
                context.reset({playerInfo: req.body.data, reason: '应用玩家数据失败: ' + statusMessage});
                return context.setState(STATES.SYNC_FAILED);
            }
        }

        // 未获取到玩家数据
        if (req.body.statusMessage == '主服无此玩家数据'){
             /** 🔄️✅同步成功 - 允许存档新建玩家 */
            if (CONF.CONFIG.get('SyncConfig')['AllowCreateNewPlayer']){
                WSC.logger.info(`[新玩家副服登录][配置:允许主服新建玩家] ${req.body.data.realName} 登录成功`);
                WSC.client.sendServerLogger(`[新玩家副服登录][配置:允许主服新建玩家] ${req.body.data.realName} 登录成功`);
                context.reset({playerInfo: req.body.data});
                return context.setState(STATES.SYNC_SUCCESS);
            }
            
            let mainBDSClientInfo = Object.values(req.data.onlineClientsInfo).find(clientInfo=>clientInfo?.bds?.syncType == 'main');
            
            let pl = mc.getPlayer(req.data.xuid);

            /** 🔄️❌同步失败 - 玩家已下线 */
            if (pl == null){
                context.reset({playerInfo: req.body.data, reason: '同步失败, 玩家已下线'});
                return context.setState(STATES.SYNC_FAILED);
            }
            
            /** 🔄️🚀同步传送 - 新玩家传送到主服 */
            context.reset({
                playerInfo: req.body.data, 
                reason: '新玩家传送到主服', 
                ip: mainBDSClientInfo.bds.ip, 
                port: mainBDSClientInfo.bds.port
            });
            return context.setState(STATES.SYNC_TRANSFER);
        }
        
        /** 🔄️❌同步失败 - 请求玩家数据失败 */
        context.reset({playerInfo: req.body.data, reason: req.body.statusMessage});
        return context.setState(STATES.SYNC_FAILED);
        
    },
    
    //======================================================


    /** 🔄️🚀同步传送 */
    [STATES.SYNC_TRANSFER]: async ({ context, playerInfo, reason, ip, port}) => {
        mc.getPlayer(playerInfo.uuid)?.transServer(ip, port);
        WSC.logger.warn(`[登录同步传送] ${playerInfo.realName} ${reason}`);
        WSC.client.sendServerLogger(`[登录同步传送] ${playerInfo.realName} ${reason}`, 'warn');
        context.setState(null);
        return null;
    },
    

    /** ❌同步失败 */
    [STATES.SYNC_FAILED]: async ({ context, playerInfo, reason, level}) => {
        if (playerInfo?.xuid == null){return false;}
        let pl = mc.getPlayer(playerInfo.xuid);
        if (pl == null){return false;}
        pl.disconnect(reason);
        if (level == 'error'){
            WSC.logger.error(`[登录同步失败] ${playerInfo.realName} ${reason}`);
            WSC.client?.sendServerLogger(`[登录同步失败] ${playerInfo.realName} ${reason}`, 'error');
        }else{
            WSC.logger.warn(`[登录同步失败] ${playerInfo.realName} ${reason}`);
            WSC.client?.sendServerLogger(`[登录同步失败] ${playerInfo.realName} ${reason}`, 'warn');
        }
        context.setState(null);
        return null;
    },
    

    /** ✅同步成功 */
    [STATES.SYNC_SUCCESS]: async ({ context, playerInfo}) => {
        // 解除冻结
        // 发送同步成功事件
        WSC.client.sendJson(new Response()
            .setUrl("/bds/sync/loginSync/result")
            .setData({
                xuid: playerInfo.xuid,
                uuid: playerInfo.uuid,
                realName: playerInfo.realName
            })
        );

        // 更新同步状态
        WSC.client.playerLoginSyncSuccess[playerInfo.uuid] = true;
        WSC.logger.info(`[登录同步成功] ${playerInfo.realName} 登录同步已完成`);
        WSC.client.sendServerLogger(`[登录同步成功] ${playerInfo.realName} 登录同步已完成`);
        context.setState(null);
        return null;
    }
};




// ==============================

const syncMappings = [
    {
        option: 'syncInventory',
        log: '背包',
        tags: ['Inventory', 'Armor', 'Offhand']
    },
    {
        option: 'syncTags',
        log: 'tag',
        tags: ['Tags']
    },
    {
        option: 'syncEnderChest',
        log: '末影箱',
        tags: ['EnderChestInventory']
    },
    {
        option: 'syncLevel',
        log: '等级',
        tags: ['PlayerLevel', 'PlayerLevelProgress']
    },
    {
        option: 'syncEffects',
        log: '效果',
        tags: ['ActiveEffects']
    },
    {
        option: 'syncAttributes',
        log: '属性',
        tags: ['Attributes']
    }
];

// 获取本服玩家数据
function getPlayerData(playerInfo){
    let SyncSendOption = CONF.CONFIG.get('SyncConfig')['SyncSendOption'];
    let score = {};
    for (let scoreName of GMLIB_SC.getAllObjectives()){
        if (SyncSendOption['syncScoreboard'].includes(scoreName)){
            score[scoreName] = {displayName: GMLIB_SC.getDisplayName(scoreName), value: GMLIB_SC.getPlayerScore(playerInfo.uuid, scoreName)};
        }
    }

    let playerNBT = mc.getPlayer(playerInfo.xuid)?.getNbt();
    if (playerNBT == null){
        if (!GMLIB_MC.hasPlayerNbt(playerInfo.uuid)){
            return null;
        }
        playerNBT = GMLIB_MC.getPlayerNbt(playerInfo.uuid);
        if (playerNBT == null){
            return null;
        }
    }

    // let nbtData = new NbtCompound(Object.entries({
    //     "Inventory": SyncSendOption["syncInventory"] ? playerNBT.getTag("Inventory") : undefined,
    //     "Armor": SyncSendOption["syncInventory"] ? playerNBT.getTag("Armor") : undefined,
    //     "Offhand": SyncSendOption["syncInventory"] ? playerNBT.getTag("Offhand") : undefined,
    //     "Tags": SyncSendOption["syncTags"] ? playerNBT.getTag("Tags") : undefined,
    //     "EnderChestInventory": SyncSendOption["syncEnderChest"] ? playerNBT.getTag("EnderChestInventory") : undefined,
    //     "PlayerLevel": SyncSendOption["syncLevel"] ? playerNBT.getTag("PlayerLevel") : undefined,
    //     "PlayerLevelProgress": SyncSendOption["syncLevel"] ? playerNBT.getTag("PlayerLevelProgress") : undefined,
    //     "Attributes": SyncSendOption["syncEffects"] ? playerNBT.getTag("Attributes") : undefined,
    //     "ActiveEffects": SyncSendOption["syncEffects"] ? playerNBT.getTag("ActiveEffects") : undefined,
    // }).filter(([key, value]) => value != undefined));

    let nbtData = new NbtCompound();
    syncMappings.forEach(mapping => {
        if (SyncSendOption[mapping.option] === true) {
            mapping.tags.forEach(tag => {
                if (playerNBT.getTag(tag) != null) {
                    WSC.logger.debug(`获取 ${mapping.log} ${tag}`);
                    nbtData.setTag(tag, playerNBT.getTag(tag));
                }else{
                    WSC.logger.debug(`获取 ${mapping.log} ${tag} 失败`);
                }
            });
        }
    });

    let playerData = {
        // nbtData: {
        //     "Inventory": playerNBT.getTag("Inventory"),
        //     "Armor": playerNBT.getTag("Armor"),
        //     "Offhand": playerNBT.getTag("Offhand"),
        //     "Tags": playerNBT.getTag("Tags"),
        //     "EnderChestInventory": playerNBT.getTag("EnderChestInventory"),
        //     "PlayerLevel": playerNBT.getTag("PlayerLevel"),
        //     "PlayerLevelProgress": playerNBT.getTag("PlayerLevelProgress"),
        //     "Attributes": playerNBT.getTag("Attributes"),
        //     "ActiveEffects": playerNBT.getTag("ActiveEffects"),
        // },
        //playerNBT: data.toBase64(GMLIB_MC.getPlayerNbt(playerInfo.uuid).toBinaryNBT()),
        nbtData: data.toBase64(nbtData.toBinaryNBT()),
        LLMoney: SyncSendOption['syncLLMoney'] ? money.get(playerInfo.xuid) : undefined,
        scoreboard: score,
    };
    return playerData;
}

// 应用玩家数据
function setPlayerData(playerInfo, playerData){
    let result = {
        success: true,
        statusCode: 0,
        statusMessage: "",
        playerInfo: playerInfo,
    };

    // 存档没有该玩家 则 创建新玩家
    if (!GMLIB_MC.hasPlayerNbt(playerInfo.uuid) && mc.getPlayer(playerInfo.uuid)?.getNbt()==null){ 
        let SyncReceiveOption = CONF.CONFIG.get('SyncConfig')['SyncReceiveOption'];
        if (!Object.values(SyncReceiveOption).some(v=>v===true) || 
            (CONF.CONFIG.get('SyncConfig')['SyncReceiveOption']['syncScoreboard'].length != 0 && playerData.scoreboard != null)
        ){
            if (!CONF.CONFIG.get('SyncConfig')["AllowCreateNewPlayer"]){
                WSC.logger.error(`[存档写入] <${playerInfo.realName}> 找不到玩家NBT, 无法同步`);
                result.success = false;
                result.statusCode = 1;
                result.statusMessage = "找不到玩家NBT, 无法同步, 新玩家请尝试重新进服";
                return result;
            }
        }
        let initNbt_str = File.readFrom('./plugins/CoordinationWsc/src/utils/initPlayerNbt');
        if (initNbt_str == null){
            result.success = false;
            result.statusCode = 2;
            result.statusMessage = "找不到初始化NBT, 无法同步";
            return result;
        }
        let nbt = NBT.parseBinaryNBT(data.fromBase64(initNbt_str, true));
        let wordSpawnPos = GMLIB_MC.getWorldSpawn();
        nbt.setTag("SpawnBlockPositionX", new NbtInt(Math.floor(wordSpawnPos.x)));
        nbt.setTag("SpawnBlockPositionY", new NbtInt(Math.floor(wordSpawnPos.y)));
        nbt.setTag("SpawnBlockPositionZ", new NbtInt(Math.floor(wordSpawnPos.z)));
        nbt.setTag("SpawnX", new NbtInt(Math.floor(wordSpawnPos.x)));
        nbt.setTag("SpawnY", new NbtInt(Math.floor(wordSpawnPos.y)));
        nbt.setTag("SpawnZ", new NbtInt(Math.floor(wordSpawnPos.z)));
        result.success = GMLIB_MC.setPlayerNbt(playerInfo.uuid, nbt);
        if (!result.success){
            WSC.logger.error(`[存档写入] 新玩家 <${playerInfo.realName}> NBT写入失败`);
            result.statusCode = 3;
            result.statusMessage = "新玩家NBT写入失败";
            return result;
        }
        GMLIB_MC.setPlayerPosition(playerInfo.uuid, wordSpawnPos);
        //GMLIB_MC.setPlayerSpawnPoint(playerInfo.uuid, wordSpawnPos);
    }

    // [写入计分板]
    if (CONF.CONFIG.get('SyncConfig')['SyncReceiveOption']['syncScoreboard'].length != 0 && playerData.scoreboard != null){
        let score = playerData.scoreboard;
        for (let scoreName in score){
            //按配置同步相应计分项
            if (!CONF.CONFIG.get('SyncConfig')['SyncReceiveOption']['syncScoreboard'].includes(scoreName)){continue;} 
            let allScoreName = GMLIB_SC.getAllObjectives();

            //计分板不存在则新建该计分板
            if (!allScoreName.includes(scoreName)){
                result.success = result.success && GMLIB_SC.addObjective(scoreName, score[scoreName].displayName);
                if (!result.success){
                    WSC.logger.error(`[存档写入] 新建计分板项目失败: ${scoreName} ${score[scoreName]?.displayName}`);
                }
            }

            //该玩家没有分数则重置该玩家分数
            if (score[scoreName].value == null){
                result.success = result.success && GMLIB_SC.resetPlayerScore(playerInfo.uuid, scoreName);
                if (!result.success){
                    WSC.logger.error(`[存档写入] 重置玩家计分板分数失败: ${playerInfo.realName} ${scoreName}`);
                }
                continue;
            }

            // 同步该玩家分数
            result.success = result.success && GMLIB_SC.setPlayerScore(playerInfo.uuid, scoreName, score[scoreName].value);
            if (result.success){
                WSC.logger.debug(`[存档写入] <${playerInfo.realName}> scoreboard 写入成功`);
            }else{
                WSC.logger.error(`[存档写入] scoreboard写入失败 ${playerInfo.realName} ${scoreName} ${score[scoreName].value}`);
                WSC.logger.error(score);
                result.statusCode = 4;
                result.statusMessage = "计分板写入失败";
                return result;
            }
        }
    }
    
    // [写入LLMoney]
    if (CONF.CONFIG.get('SyncConfig')['SyncReceiveOption']['syncLLMoney'] && playerData.LLMoney != null) {
        try {
            if (money.get(playerInfo.xuid) != playerData.LLMoney){
                WSC.client.syncLLMoneyStatus.add(playerInfo.xuid);
                money.set(playerInfo.xuid, playerData.LLMoney);
                WSC.client.syncLLMoneyStatus.delete(playerInfo.xuid);
            }
            WSC.logger.debug(`[存档写入] <${playerInfo.realName}> LLMoney写入成功`);
        }
        catch(e) {
            result.success = false;
            WSC.logger.error(`[存档写入] <${playerInfo.realName}> LLMoney写入失败 `,e);
        }
    }
    
    // [写入存档NBT]
    let SyncReceiveOption = CONF.CONFIG.get('SyncConfig')['SyncReceiveOption'];
    if (Object.values(SyncReceiveOption).some(v=>v==true)){
        let nbtData = NBT.parseBinaryNBT(data.fromBase64(playerData.nbtData, true));
        if (nbtData == null){
            //File.writeTo("./plugins/chatwss/illegalNbt.txt", playerData.playerNBT);
            WSC.logger.error(`[存档写入] <${playerInfo.realName}> NBT解析错误, xuid: ${playerInfo.xuid}, uuid: ${playerInfo.uuid}`);
            result.success = false;
            result.statusCode = 5;
            result.statusMessage = "NBT解析错误";
            return result;
        }

        // 在线玩家判断
        let pl = mc.getPlayer(playerInfo.xuid);
        let onlineNbt = pl?.getNbt();
        if (onlineNbt != null){
            result.success = setOnlineNbt(SyncReceiveOption, nbtData, pl);
        }else{
            result.success = setNbt(SyncReceiveOption, nbtData, playerInfo);
        }

        // 结果
        if (!result.success){
            result.statusCode = 6;
            result.statusMessage = "NBT写入失败";
            WSC.logger.error(`[存档写入] <${playerInfo.realName}> NBT写入失败`);
        }else{
            WSC.logger.debug(`[存档写入] <${playerInfo.realName}> NBT写入成功: ${playerInfo.uuid} `);
        }
        mc.getPlayer(playerInfo.uuid)?.refreshItems();
    }

    //logger.debug(`[存档写入] <${playerInfo.realName}> NBTtag配置: ${getSOArray(syncConfig)}`);
    return result;
}

function setOnlineNbt(SyncReceiveOption, nbtData, pl){
    let onlineNbt = pl.getNbt();
    syncMappings.forEach(mapping => {
        if (SyncReceiveOption[mapping.option] === true) {
            mapping.tags.forEach(tag => {
                if (nbtData.getTag(tag) != null) {
                    onlineNbt.setTag(tag, nbtData.getTag(tag));
                }
            });
        }
    });
    return pl.setNbt(onlineNbt);
}

function setNbt(SyncReceiveOption, nbtData, playerInfo){
    let tagList = [];
    syncMappings.forEach(mapping => {
        if (SyncReceiveOption[mapping.option] === true) {
            mapping.tags.forEach(tag => {
                if (nbtData.getTag(tag) != null) {
                    tagList.push(tag);
                }
            });
        }
    });
    return GMLIB_MC.setPlayerNbtTags(playerInfo.uuid, nbtData, tagList);
}

module.exports = {
    getPlayerData,
    setPlayerData
};